Working with JavaScript objects
Pulling in fully-formed
HTML on demand is very convenient, but there are times when we want our
script to be able to do some processing of the data before it is
displayed. In this case, we need to retrieve the data in a structure
that we can traverse with JavaScript.
With jQuery's selectors, we
could traverse the HTML we get back and manipulate it, but it must first
be inserted into the document. A more native JavaScript data format can
mean even less code.
Retrieving a JavaScript object
As we have often seen, JavaScript objects are just sets of key-value pairs, and can be defined succinctly using curly braces ({}). JavaScript arrays,
on the other hand, are defined on the fly with square brackets ([]).
Combining these two concepts, we can easily express some very complex
and rich data structures.
The term JavaScript Object Notation
(JSON) was coined by Douglas Crockford to capitalize on this simple
syntax. This notation can offer a concise alternative to the
sometimes-bulky XML format:
{
"key": "value",
"key 2": [
"array",
"of",
"items"
]
}
For information on some of the potential advantages of JSON, as well as implementations in many programming languages, visit http://json.org/.
We can encode our data using this format in many ways. We'll place some dictionary entries in a JSON file we'll call b.json, which begins as follows:
[
{
JavaScript objectretrieving"term": "BACCHUS",
"part": "n.",
"definition": "A convenient deity invented by the...",
"quote": [
"Is public worship, then, a sin,",
"That for devotions paid to Bacchus",
"The lictors dare to run us in,",
"And resolutely thump and whack us?"
],
"author": "Jorace"
},
{
"term": "BACKBITE",
"part": "v.t.",
"definition": "To speak of a man as you find him when..."
},
{
"term": "BEARD",
"part": "n.",
"definition": "The hair that is commonly cut off by..."
},
To retrieve this data, we'll use the $.getJSON() method, which fetches the file and processes it, providing the calling code with the resulting JavaScript object.
Global jQuery functions
To this point, all jQuery methods that we've used have been attached to a jQuery object that we've built with the $()
factory function. The selectors have allowed us to specify a set of DOM
nodes to work with, and the methods have operated on them in some way.
This $.getJSON() function, however, is
different. There is no logical DOM element to which it could apply; the
resulting object has to be provided to the script, not injected into
the page. For this reason, getJSON() is defined as a method of the global jQuery object (a single object called jQuery or $ defined once by the jQuery library), rather than of an individual jQuery object instance (the objects we create with the $() function).
If JavaScript had classes like other object-oriented languages, we'd call $.getJSON() a class method. For our purposes, we'll refer to this type of method as a global function; in effect, they are functions that use the jQuery namespace so as not to conflict with other function names.
To use this function, we pass it the file name as before:
$(document).ready(function() {
$('#letter-b a').click(function() {
$.getJSON('b.json');
return false;
});
});
This code has no apparent
effect when we click the link. The function call loads the file, but we
have not told JavaScript what to do with the resulting data. For this,
we need to use a callback function.
The $.getJSON()
function takes a second argument, which is a function to be called when
the load is complete. As mentioned before, AJAX calls are asynchronous,
and the callback provides a way to wait for the data to be transmitted
rather than executing code right away. The callback function also takes
an argument, which is filled with the resulting data. So, we can write:
$(document).ready(function() {
$('#letter-b a').click(function() {
$.getJSON('b.json', function(data) {
});
return false;
});
});
Here we are using an anonymous function as our callback, as has been common in our jQuery code for brevity. A named function could equally be provided as the callback.
Inside this function, we can use the data
variable to traverse the data structure as necessary. We'll need to
iterate over the top-level array, building the HTML for each item. We
could do this with a standard for loop, but instead we'll introduce another of jQuery's useful global functions, $.each(). Instead of operating on a jQuery object, this
function takes an array or map as its first parameter and a callback
function as its second. Each time through the loop, the current iteration index and the current item in the array or map are passed as two parameters to the callback function.
$(document).ready(function() {
JavaScript objectglobal Query object$('#letter-b a').click(function() {
$.getJSON('b.json', function(data) {
$('#dictionary').empty();
$.each(data, function(entryIndex, entry) {
var html = '<div class="entry">';
html += '<h3 class="term">' + entry['term'] + '</h3>';
html += '<div class="part">' + entry['part'] + '</div>';
html += '<div class="definition">';
html += entry['definition'];
html += '</div>';
html += '</div>';
$('#dictionary').append(html);
});
});
return false;
});
});
Before the loop, we empty out<div id="dictionary"> so that we can fill it with our newly-constructed HTML. Then we use $.each()
to examine each item in turn, building an HTML structure using the
contents of the entry map. Finally, we turn this HTML into a DOM tree by
appending it to the<div>.
This approach presumes that the data is safe for HTML consumption; it should not contain any stray< characters, for example.
All that's left is to handle the entries with quotations, which takes another $.each() loop:
$(document).ready(function() {
$('#letter-b a').click(function() {
$.getJSON('b.json', function(data) {
$('#dictionary').empty();
$.each(data, function(entryIndex, entry) {
var html = '<div class="entry">';
html += '<h3 class="term">' + entry['term'] + '</h3>';
html += '<div class="part">' + entry['part'] + '</div>';
html += '<div class="definition">';
html += entry['definition'];
if (entry['quote']) {
html += '<div class="quote">';
$.each(entry['quote'], function(lineIndex, line) {
html += '<div class="quote-line">' + line + '</div>';
});
if (entry['author']) {
html += '<div class="quote-author">' + entry['author'] + '</div>';
}
html += '</div>';
}
html += '</div>';
html += '</div>';
$('#dictionary').append(html);
});
});
return false;
});
});
With this code in place, we can click the B link and confirm our results:
The JSON format is
concise, but not forgiving. Every bracket, brace, quote, and comma must
be present and accounted for, or the file will not load. In most
browsers, we won't even get an error message; the script will just
silently fail.
Executing a script
Occasionally we don't want to
retrieve all the JavaScript we will need when the page is first loaded.
We might not know what scripts will be necessary until some user
interaction occurs. We could introduce<script> tags on the fly when they are needed, but a more elegant way to inject additional code is to have jQuery load the .js file directly.
Pulling in a script is about as simple as loading an HTML fragment. In this case, we use the global function $.getScript(), which, like its siblings, accepts a URL locating the script file:
$(document).ready(function() {
$('#letter-c a').click(function() {
$.getScript('c.js');
return false;
});
});
In our last example, we
then needed to process the result data so that we could do something
useful with the loaded file. With a script file, though, the processing
is automatic; the script is simply run.
Scripts fetched in this way are run in the global context
of the current page. This means they have access to all
globally-defined functions and variables, notably including jQuery
itself. We can therefore mimic the JSON example to prepare and insert
HTML on the page when the script is executed, and place this code in c.js:
var entries = [
{
"term": "CALAMITY",
"part": "n.",
"definition": "A more than commonly plain and..."
},
{
"term": "CANNIBAL",
"part": "n.",
"definition": "A gastronome of the old school who..."
},
{
"term": "CHILDHOOD",
"part": "n.",
"definition": "The period of human life intermediate..."
},
{
"term": "CLARIONET",
"part": "n.",
"definition": "An instrument of torture operated by..."
},
{
"term": "COMFORT",
"part": "n.",
"definition": "A state of mind produced by..."
},
{
"term": "CORSAIR",
"part": "n.",
"definition": "A politician of the seas."
JavaScript objectscript, executing}
];
var html = '';
$.each(entries, function() {
html += '<div class="entry">';
html += '<h3 class="term">' + this['term'] + '</h3>';
html += '<div class="part">' + this['part'] + '</div>';
html += '<div class="definition">' + this['definition'] + '</div>';
html += '</div>';
});
$('#dictionary').html(html);
Now clicking on the C link has the expected result: